﻿//------------------------------------------------------------------------------
// <copyright company="Tunynet">
//     Copyright (c) Tunynet Inc.  All rights reserved.
// </copyright> 
//------------------------------------------------------------------------------

using Lucene.Net.Documents;
using Lucene.Net.Search;
using System.Collections.Generic;
using System.Linq;
using Tunynet;
using Tunynet.Post;
using Tunynet.Search;

namespace Tunynet.Post
{
    /// <summary>
    /// 贴吧搜索器
    /// </summary>
    public class ThreadSearcher : ISearcher
    {
        private ThreadService ThreadService = DIContainer.Resolve<ThreadService>();
        private IKvStore kvStore = DIContainer.Resolve<IKvStore>();

        private ISearchEngine searchEngine;

        /// <summary>
        /// code
        /// </summary>
        public static string CODE = "ThreadSearcher";

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="name"></param>
        /// <param name="indexPath"></param>
        /// <param name="displayOrder"></param>
        public ThreadSearcher(string name, string indexPath, int displayOrder)
        {
            this.Name = name;
            this.IndexPath = Tunynet.Utilities.WebUtility.GetPhysicalFilePath(indexPath);
            this.DisplayOrder = displayOrder;
            searchEngine = SearcherFactory.GetSearchEngine(indexPath);
        }

        #region 搜索器属性

        /// <summary>
        /// 关联的搜索引擎实例
        /// </summary>
        public ISearchEngine SearchEngine
        {
            get
            {
                return searchEngine;
            }
        }

        /// <summary>
        /// 搜索器的唯一标识
        /// </summary>
        public string Code { get { return CODE; } }

        /// <summary>
        /// 显示顺序
        /// </summary>
        public int DisplayOrder { get; private set; }

        /// <summary>
        /// Lucene索引路径（完整物理路径，支持unc）
        /// </summary>
        public string IndexPath { get; private set; }

        /// <summary>
        /// 名称
        /// </summary>
        public string Name { get; private set; }

        #endregion 搜索器属性

        #region 索引内容维护

        /// <summary>
        /// 重建索引
        /// </summary>
        public void RebuildIndex()
        {
            bool hasData = false;

            //pageSize参数决定了每次批量取多少条数据进行索引。要注意的是，如果是分布式搜索，客户端会将这部分数据通过WCF传递给服务器端，而WCF默认的最大传输数据量是65535B，pageSize较大时这个设置显然是不够用的，WCF会报400错误；系统现在将最大传输量放宽了，但仍要注意一次不要传输过多，如遇异常，可适当调小pageSize的值
            int pageSizeThread = 100;
            int pageIndexThread = 1;
            long totalRecordsThread = 0;
            bool isBeginningThread = true;
            bool isEnddingThread = false;
            ThreadQuery ThreadQuery = new ThreadQuery();
            do
            {
                //分页获取贴子列表
                PagingDataSet<Thread> Threads = ThreadService.Gets(null, ThreadQuery, pageSizeThread, pageIndexThread);
                totalRecordsThread = Threads.TotalRecords;
                if (totalRecordsThread > 0)
                {
                    hasData = true;
                }
                isEnddingThread = (pageSizeThread * pageIndexThread < totalRecordsThread) ? false : true;

                //重建索引
                List<Thread> threadList = Threads.ToList<Thread>();

                IEnumerable<Document> docs = ThreadIndexDocument.Convert(threadList);

                searchEngine.RebuildIndex(docs, isBeginningThread, false);

                isBeginningThread = false;
                pageIndexThread++;
            }
            while (!isEnddingThread);

            if (hasData)
            {
                searchEngine.RebuildIndex(null, false, true);
            }
        }

        /// <summary>
        /// 添加索引
        /// </summary>
        /// <param name="Thread">待添加的发贴</param>
        public void Insert(Document Thread)
        {
            Insert(new Document[] { Thread });
        }

        /// <summary>
        /// 添加索引
        /// </summary>
        /// <param name="Threads">待添加的发贴</param>
        public void Insert(IEnumerable<Document> Threads)
        {
            searchEngine.Insert(Threads);
        }

        /// <summary>
        /// 删除索引
        /// </summary>
        /// <param name="threadId">待删除的发贴Id</param>
        public void Delete(long threadId)
        {
            searchEngine.Delete(threadId.ToString(), ThreadIndexDocument.ThreadId);
        }

        /// <summary>
        /// 删除索引
        /// </summary>
        /// <param name="threadIds">待删除的发贴Id列表</param>
        public void Delete(IEnumerable<long> threadIds)
        {
            foreach (var ThreadId in threadIds)
            {
                searchEngine.Delete(ThreadId.ToString(), ThreadIndexDocument.ThreadId);
            }
        }

        /// <summary>
        /// 更新索引
        /// </summary>
        /// <param name="Thread">待更新的发贴</param>
        public void Update(Thread Thread)
        {
            Document doc = ThreadIndexDocument.Convert(Thread);
            searchEngine.Update(doc, Thread.ThreadId.ToString(), ThreadIndexDocument.ThreadId);
        }

        /// <summary>
        /// 更新索引
        /// </summary>
        /// <param name="Threads">待更新的发贴集合</param>
        public void Update(IEnumerable<Thread> Threads)
        {
            IEnumerable<Document> docs = ThreadIndexDocument.Convert(Threads);
            IEnumerable<string> ThreadIds = Threads.Select(n => n.ThreadId.ToString());
            searchEngine.Update(docs, ThreadIds, ThreadIndexDocument.ThreadId);
        }

        #endregion 索引内容维护

        #region 索引定时任务维护

        /// <summary>
        /// 定时任务操作
        /// </summary>
        public void SearchTask()
        {
            //添加索引
            var insertValues = kvStore.Pop(KvKeys.Instance().ThreadSearch(), 100);

            foreach (var value in insertValues)
            {
                long threadId = 0;

                value.TryParse<long>(out threadId);
                if (threadId > 0)
                {
                    var thread = ThreadService.Get(threadId);
                    if (thread != null)
                        Insert(ThreadIndexDocument.Convert(thread));
                }
            }

            //删除索引
            var deleteValues = kvStore.Pop(KvKeys.Instance().ThreadDeleteSearch(), 100);

            foreach (var value in deleteValues)
            {
                long threadId = 0;
                value.TryParse<long>(out threadId);

                if (threadId > 0)
                {
                    Delete(threadId);
                }
            }

            var updateValues = kvStore.Pop(KvKeys.Instance().ThreadUpdateSearch(), 100);

            foreach (var value in updateValues)
            {
                long threadId = 0;

                value.TryParse<long>(out threadId);
                if (threadId > 0)
                {
                    var thread = ThreadService.Get(threadId);
                    if (thread != null)
                        Update(thread);
                }
            }

            Commit();
        }

        /// <summary>
        /// 提交索引变更
        /// </summary>
        public void Commit()
        {
            searchEngine.Commit();
        }

        #endregion 索引定时任务维护

        #region 搜索

        /// <summary>
        /// 贴子分页搜索
        /// </summary>
        /// <param name="threadQuery">搜索条件</param>
        /// <returns>符合搜索条件的分页集合</returns>
        public PagingDataSet<Thread> Search(ThreadFullTextQuery threadQuery)
        {
            if (string.IsNullOrWhiteSpace(threadQuery.Keyword))
            {
                return new PagingDataSet<Thread>(new List<Thread>());
            }
            var keywords = ClauseScrubber.SegmentForPhraseQuery(threadQuery.Keyword);
            List<Document> searchResults = new List<Document>();

            foreach (var keyword in keywords)
            {
                threadQuery.Keyword = keyword;
                LuceneSearchBuilder searchBuilder = BuildLuceneSearchBuilder(threadQuery);

                //使用LuceneSearchBuilder构建Lucene需要Query、Filter、Sort
                Query query = null;
                Filter filter = null;
                Sort sort = null;
                searchBuilder.BuildQuery(out query, out filter, out sort);

                //调用SearchService.Search(),执行搜索
                searchResults.AddRange(searchEngine.Search(query, filter, sort));
            }

            //解析出搜索结果中的资讯ID
            List<long> threadIds = new List<long>();

            foreach (Document doc in searchResults)
            {
                long threadId = long.Parse(doc.Get(ThreadIndexDocument.ThreadId));
                if (!threadIds.Contains(threadId))
                    threadIds.Add(threadId);
            }

            var ids = threadIds.Skip(threadQuery.PageSize * (threadQuery.PageIndex - 1)).Take(threadQuery.PageSize);

            IEnumerable<Thread> threadLists = ThreadService.Gets(ids);

            //组装分页对象
            PagingDataSet<Thread> threads = new PagingDataSet<Thread>(threadLists)
            {
                TotalRecords = ids.LongCount(),
                PageSize = threadQuery.PageSize,
                PageIndex = threadQuery.PageIndex
            };

            return threads;
        }

        /// <summary>
        /// 贴子Id分页搜索(针对于需要在页面重新组装成实体的,减少一次数据库查询,直接返回ID集合)
        /// </summary>
        /// <param name="threadQuery">搜索条件</param>
        /// <returns>符合搜索条件的分页集合</returns>
        public PagingDataSet<long> SearchThreadIds(ThreadFullTextQuery threadQuery)
        {
            if (string.IsNullOrWhiteSpace(threadQuery.Keyword))
            {
                return new PagingDataSet<long>(new List<long>());
            }

            LuceneSearchBuilder searchBuilder = BuildLuceneSearchBuilder(threadQuery);

            //使用LuceneSearchBuilder构建Lucene需要Query、Filter、Sort
            Query query = null;
            Filter filter = null;
            Sort sort = null;
            searchBuilder.BuildQuery(out query, out filter, out sort);

            //调用SearchService.Search(),执行搜索
            //PagingDataSet<Document> searchResult = searchEngine.Search(query, filter, sort, threadQuery.PageIndex, threadQuery.PageSize);
            //IEnumerable<Document> docs = searchResult.ToList<Document>();

            var searchResults = searchEngine.Search(query, filter, sort);

            //解析出搜索结果中的资讯ID
            List<long> threadIds = new List<long>();
            foreach (Document doc in searchResults)
            {
                long threadId = long.Parse(doc.Get(ThreadIndexDocument.ThreadId));
                if (!threadIds.Contains(threadId))
                    threadIds.Add(threadId);
            }

            var ids = threadIds.Skip(threadQuery.PageSize * (threadQuery.PageIndex - 1)).Take(threadQuery.PageSize);

            //组装分页对象
            return new PagingDataSet<long>(ids)
            {
                TotalRecords = threadIds.LongCount(),
                PageSize = threadQuery.PageSize,
                PageIndex = threadQuery.PageIndex
            };
        }

        /// <summary>
        /// 根据搜索查询条件构建Lucene查询条件
        /// </summary>
        /// <param name="threadQuery"></param>
        /// <returns></returns>
        private LuceneSearchBuilder BuildLuceneSearchBuilder(ThreadFullTextQuery threadQuery)
        {
            LuceneSearchBuilder searchBuilder = new LuceneSearchBuilder();
            //微博搜索词匹配范围
            //搜索词匹配范围
            Dictionary<string, BoostLevel> fieldNameAndBoosts = new Dictionary<string, BoostLevel>();
            switch (threadQuery.Range)
            {
                case SearchRange.SUBJECT:
                    searchBuilder.WithPhrase(ThreadIndexDocument.Subject, threadQuery.Keyword, BoostLevel.Hight, false);
                    break;

                case SearchRange.BODY:
                    fieldNameAndBoosts.Add(ThreadIndexDocument.Body, BoostLevel.Hight);
                    fieldNameAndBoosts.Add(ThreadIndexDocument.Subject, BoostLevel.Medium);
                    searchBuilder.WithPhrases(fieldNameAndBoosts, threadQuery.Keyword, Occur.SHOULD, false);
                    break;

                case SearchRange.AUTHOR:
                    searchBuilder.WithPhrase(ThreadIndexDocument.UserId, threadQuery.Keyword, BoostLevel.Hight, false);
                    break;

               default:
                    fieldNameAndBoosts.Add(ThreadIndexDocument.Subject, BoostLevel.Hight);
                    fieldNameAndBoosts.Add(ThreadIndexDocument.Body, BoostLevel.Medium);
                    fieldNameAndBoosts.Add(ThreadIndexDocument.UserId, BoostLevel.Low);
                    searchBuilder.WithPhrases(fieldNameAndBoosts, threadQuery.Keyword, Occur.SHOULD, false);
                    break;
            }

            //筛选租户类型
            if (!string.IsNullOrEmpty(threadQuery.TenantTypeId))
            {
                searchBuilder.WithField(ThreadIndexDocument.TenantTypeId, threadQuery.TenantTypeId, true, BoostLevel.Hight, true);
            }

            //贴吧搜索条件过滤(全吧、吧内)
            if (threadQuery.SectionId != "-1")
            {
                searchBuilder.WithField(ThreadIndexDocument.SectionId, threadQuery.SectionId, true, BoostLevel.Hight, true);
            }

            if (threadQuery.MinDate.HasValue && threadQuery.MaxDate.HasValue)
            {
                searchBuilder.WithinRange(ThreadIndexDocument.DateCreated, DateTools.DateToString(threadQuery.MinDate.Value, DateTools.Resolution.MINUTE), DateTools.DateToString(threadQuery.MaxDate.Value, DateTools.Resolution.MINUTE));
            }

            if (!threadQuery.IsDefaultOrder)
            {
                //贴吧排序
                searchBuilder.SortByString(ThreadIndexDocument.DateCreated, true);
            }

            if (threadQuery.PubliclyAuditStatus.HasValue)
            {
                switch (threadQuery.PubliclyAuditStatus.Value)
                {
                    case Tunynet.Common.PubliclyAuditStatus.Again:
                        searchBuilder.WithinRange(ThreadIndexDocument.ApprovalStatus, ((int)Tunynet.Common.PubliclyAuditStatus.Pending).ToString(), ((int)Tunynet.Common.PubliclyAuditStatus.Success).ToString());
                        break;

                    case Tunynet.Common.PubliclyAuditStatus.Fail:
                    case Tunynet.Common.PubliclyAuditStatus.Pending_GreaterThanOrEqual:
                    case Tunynet.Common.PubliclyAuditStatus.Pending:
                    case Tunynet.Common.PubliclyAuditStatus.Again_GreaterThanOrEqual:
                        searchBuilder.WithinRange(ThreadIndexDocument.ApprovalStatus, ((int)Tunynet.Common.PubliclyAuditStatus.Fail).ToString(), ((int)Tunynet.Common.PubliclyAuditStatus.Success).ToString());
                        break;

                    case Tunynet.Common.PubliclyAuditStatus.Success:
                    default:
                        searchBuilder.WithinRange(ThreadIndexDocument.ApprovalStatus, ((int)Tunynet.Common.PubliclyAuditStatus.Success).ToString(), ((int)Tunynet.Common.PubliclyAuditStatus.Success).ToString());
                        break;
                }
            }

            return searchBuilder;
        }

        #endregion 搜索
    }
}